package seatiger.tomcat.catalina.loader;


import seatiger.tomcat.naming.resources.Resource;
import seatiger.tomcat.naming.resources.ResourceAttributes;

import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 类加载器
 */
public class WebappClassLoader extends ClassLoader implements Reloader {
    /**
     * 父类加载器
     */
    private ClassLoader parent;
    /**
     * 系统类加载器
     */
    private ClassLoader system;

    /**
     * 在加载之前是否委派自己的父类查找
     */
    protected boolean delegate = false;

    private Map<String,ResourceEntry> resourceEntries = new HashMap<>();

    private DirContext resources;

    /**
     * 类加载器的参考 ，默认就是 WEB-INFO/class
     */
    private List<String> repositories = new ArrayList<>();

    /**
     * 仓库的位置
     * System.getProperty("catalina.base") + docBase + repositorie
     */
    private List<File> files = new ArrayList<>();

    private static final String[] packageTriggers = {
            "javax",                                     // Java extensions
            "org.xml.sax",                               // SAX 1 & 2
            "org.w3c.dom",                               // DOM 1 & 2
            "org.apache.xerces",                         // Xerces 1 & 2
            "org.apache.xalan"                           // Xalan
    };

    public WebappClassLoader(){
        this.parent = getParent();
        this.system = getSystemClassLoader();
    }

    public WebappClassLoader(ClassLoader parent){
        super(parent);
        this.parent = getParent();
        this.system = getSystemClassLoader();
    }

    public void setResources(DirContext resources) {
        this.resources = resources;
    }

    public DirContext getResources() {
        return resources;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //验证一下是否以java开头的
        if (name == null || name.startsWith("java.")){
            throw new ClassNotFoundException(name);
        }
        String classPath = name.replace(".", File.separator) + ".class";
        ResourceEntry entry = findResourceInternal(name,classPath);
        if (entry == null || entry.binaryContent == null){
            throw new ClassNotFoundException(name);
        }
        Class<?> clazz = entry.loadedClass;
        if (clazz == null){
            synchronized (this){
                if (clazz == null){
                    clazz = defineClass(name,entry.binaryContent,0,entry.binaryContent.length);
                    entry.loadedClass = clazz;
                }
            }
        }
        return clazz;
    }

    private ResourceEntry findResourceInternal(String name,String path) {
        if (name == null || path == null){
            return null;
        }
        //从缓存中查找
        ResourceEntry entry = resourceEntries.get(name);
        if (entry != null){
            return entry;
        }
        Resource resource = null;
        InputStream binaryStream = null;
        long contextLength = -1;

        int repositoriesLength = repositories.size();

        for (int i = 0; (entry == null) && (i < repositoriesLength); i++){
            try {
                String fullPath = repositories.get(i) + path;
                //从目录上下文中 找到Resource对象
                Object lookupResult = resources.lookup(fullPath);
                if (lookupResult instanceof Resource){
                    resource = (Resource) lookupResult;
                }
                entry = new ResourceEntry();
                entry.fullPath = fullPath;
                try {
                    entry.source = getURL(new File(files.get(i), path));
                } catch (MalformedURLException e) {
                    return null;
                }
                //获取属性
                ResourceAttributes attributes =
                        (ResourceAttributes) resources.getAttributes(fullPath);
                contextLength = attributes.getContentLength();
                entry.lastModified = attributes.getLastModified();
                if (resource != null){
                    try {
                        binaryStream = resource.streamContent();
                    } catch (IOException e) {
                        return null;
                    }
                    byte[] binaryContent = new byte[(int) contextLength];
                    try {
                        binaryStream.read(binaryContent);
                        entry.binaryContent = binaryContent;
                    }catch (IOException e){
                    }

                }

            }catch (NamingException e){
            }
        }



        if (entry != null){
            resourceEntries.put(name,entry);
        }
        return entry;
    }

    protected URL getURL(File file) throws MalformedURLException {
        File readFile = file;
        try {
            readFile = file.getCanonicalFile();
        }catch (IOException e){

        }
        return readFile.toURL();
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class<?> clazz = null;
        //判断是否在 Map<String,ResourceEntry> 中有缓存
        clazz = findLoadedClass0(name);
        //是否需要初始化
        if ( clazz != null){
            return resolveClass0(clazz,resolve);
        }
        // 判断这个类是否已近被加载过了
        clazz = findLoadedClass(name);
        if (clazz != null){
            return resolveClass0(clazz,resolve);
        }

        //系统类加载器中加载
        try {
            clazz = system.loadClass(name);
            if (clazz != null){
                return resolveClass0(clazz,resolve);
            }
        }catch (ClassNotFoundException e){

        }
        boolean delegateLoad = delegate || filter(name);
        if (delegateLoad){
            ClassLoader loader = parent == null ? system : parent;
            try {
                clazz = loader.loadClass(name);
                if (clazz != null){
                    return resolveClass0(clazz,resolve);
                }
            }catch (ClassNotFoundException e){
            }
        }

        //从项目部署的路径，web项目的路径下查找
        clazz = findClass(name);
        if (clazz != null){
            return resolveClass0(clazz,resolve);
        }

        if(!delegateLoad){
            ClassLoader loader = parent == null ? system : parent;
            try {
                clazz = loader.loadClass(name);
                if (clazz != null){
                    return resolveClass0(clazz,resolve);
                }
            }catch (ClassNotFoundException e){
            }
        }
        throw  new ClassNotFoundException(name);
    }

    private Class<?> resolveClass0(Class<?> clazz,boolean resolve){
        if (resolve){
            resolveClass(clazz);
        }
        return clazz;
    }

    /**
     * 从缓存中查找
     * @param name
     * @return
     */
    private Class<?> findLoadedClass0(String name){
        ResourceEntry entry = resourceEntries.get(name);
        return entry == null ? null : entry.loadedClass;
    }

    /**
     * 过滤掉一些特殊的类
     * @param name
     * @return
     */
    protected boolean filter(String name) {
        if (name == null)
            return false;
        String packageName = null;
        int pos = name.lastIndexOf('.');
        if (pos != -1)
            packageName = name.substring(0, pos);
        else
            return false;

        for (int i = 0; i < packageTriggers.length; i++) {
            if (packageName.startsWith(packageTriggers[i]))
                return true;
        }
        return false;
    }



    public synchronized void addRepository(String repository, File file) {
        if (repository == null){
            return;
        }
        repositories.add(repository);
        files.add(file);
    }

    @Override
    public boolean modified() {
        for (String key : resourceEntries.keySet()){
            ResourceEntry entry = resourceEntries.get(key);
            try {
                long lastModified = ((ResourceAttributes)resources.getAttributes(entry.fullPath)).getLastModified();
                System.out.println(entry.fullPath + ">>>>" + entry.lastModified +" " + lastModified);
                if (lastModified != entry.lastModified){
                    return true;
                }
            } catch (NamingException e) {
                e.printStackTrace();
            }
        }
        return false;
    }
}
